﻿#region Using directives

using System;
using System.Text;
using System.CodeDom;
using System.Collections.Generic;

using Weazel.Gezel.Model;

#endregion

namespace Weazel.Gezel.Conversions.Model
{
  /// <summary>
  /// Used to generate code for a gezel $trace directive.
  /// </summary>
  public class TracerType : Type
  {
    private Field fileNameField;
    private Field fileStreamField;

    private Method traceMethod;
    private Method initializeMethod;
    private Method cleanUpMethod;

    /// <summary>
    /// Constructs a new <see cref="TracerType"/> instance.
    /// </summary>
    /// <param name="trace"></param>
    /// <param name="softwareModel"></param>
    public TracerType(SignalTrace trace, SoftwareModel softwareModel)
      : base(string.Format("Trace_{0}", trace.Name.Replace(".", "_")), softwareModel)
    {
      if (softwareModel.Language != TargetLanguage.IsoCpp)
        fileStreamField = AddField("Stream", "StreamWriter");
      else
        fileStreamField = AddField("Stream", "ofstream");

      buildTraceMethod(trace.Expression);
      
      buildInitializeMethod(trace);

      buildCleanUpMethod();
    }

    private void buildCleanUpMethod()
    {
      cleanUpMethod = AddMethod("CleanUp");

      if (softwareModel.Language != TargetLanguage.IsoCpp)
      {

        // produces
        // Stream.Flush();
        cleanUpMethod.Statements.Add(
          new CodeMethodInvokeExpression(
            new CodeFieldReferenceExpression(
              new CodeThisReferenceExpression(),
              "Stream"
            ),
            "Flush"
          )
        );

        // produces
        // Stream.Close();
        cleanUpMethod.Statements.Add(
          new CodeMethodInvokeExpression(
            new CodeFieldReferenceExpression(
              new CodeThisReferenceExpression(),
              "Stream"
            ),
            "Close"
          )
        );
      }
      else // C++ special case
      {
        cleanUpMethod.Statements.Add(new CodeSnippetStatement("  this->Stream.close();"));
      }
    }

    /// <summary>
    /// Builds a method which initializes the tracer object.
    /// </summary>
    /// <param name="trace"></param>
    private void buildInitializeMethod(SignalTrace trace)
    {
      initializeMethod = AddMethod("Initialize");
      initializeMethod.Statements.Add(new CodeCommentStatement("don't append if file already exist"));

      if (softwareModel.Language != TargetLanguage.IsoCpp)
      {
        CodeObjectCreateExpression newStreamWriterExpression =
          new CodeObjectCreateExpression(
            "StreamWriter",
            new CodePrimitiveExpression(trace.Filename),
            new CodePrimitiveExpression(false)
          );

        CodeFieldReferenceExpression streamField =
          new CodeFieldReferenceExpression(
            new CodeThisReferenceExpression(),
            "Stream"
          );

        initializeMethod.Statements.Add(new CodeAssignStatement(streamField, newStreamWriterExpression));
      }
      else
      {
        initializeMethod.Statements.Add(
          new CodeSnippetStatement(
            string.Format("  this->Stream.open(\"{0}\", ios::out | ios::trunc);", trace.Filename)
          )
        );
      }

      // initialize integer fields
      foreach (Field field in this.Fields.Values)
      {
        IntegerField integerField = field as IntegerField;
        if (integerField != null)
        {
          if (integerField.InitialValue != "0")
          {
            initializeMethod.Statements.Add(
              new CodeAssignStatement(
                new CodeFieldReferenceExpression(
                  new CodeThisReferenceExpression(),
                  integerField.Name
                ),
                new CodeObjectCreateExpression(
                  "Integer",
                  new CodePrimitiveExpression(integerField.Width),
                  new CodePrimitiveExpression(integerField.InitialValue),
                  new CodePrimitiveExpression(integerField.Signed ? true : false)
                )
              )
            );
          }
          else
          {
            initializeMethod.Statements.Add(
              new CodeAssignStatement(
                new CodeFieldReferenceExpression(
                  new CodeThisReferenceExpression(),
                  integerField.Name
                ),
                new CodeObjectCreateExpression(
                  "Integer",
                  new CodePrimitiveExpression(integerField.Width),
                  new CodePrimitiveExpression(integerField.Signed ? true : false)
                )
              )
            );
          }
        }
      }
    }

    /// <summary>
    /// Builds a method which performs the actual tracing on an expression.
    /// </summary>
    private void buildTraceMethod(Expression expressionToTrace)
    {
      traceMethod = AddMethod("Trace");

      // add code to evaluate expression to trace
      CodeFieldReferenceExpression generatedIdentifier =
          ExpressionConversionHelper.BuildEvaluateMethodRecursive(expressionToTrace, traceMethod.Statements, this);

      if (softwareModel.Language != TargetLanguage.IsoCpp)
      {
        // Stream.WriteLine({0}.ToBinaryString());
        CodeMethodInvokeExpression toBinaryStringMethod =
          new CodeMethodInvokeExpression(generatedIdentifier, "ToBinaryString");

        CodeFieldReferenceExpression streamField =
          new CodeFieldReferenceExpression(
            new CodeThisReferenceExpression(),
            "Stream"
          );

        CodeMethodInvokeExpression writeLineMethod =
          new CodeMethodInvokeExpression(
            streamField,
            "WriteLine",
            toBinaryStringMethod
          );

        traceMethod.Statements.Add(writeLineMethod);
      }
      else
      {
        string fieldRefString = ConvertCodeFieldReferenceExpression(generatedIdentifier);
        traceMethod.Statements.Add(new CodeSnippetStatement(string.Format("  {0}->ToBinaryString(TraceBuffer, BUFFER_SIZE);", fieldRefString)));
        traceMethod.Statements.Add(new CodeSnippetStatement("  this->Stream << TraceBuffer << endl;"));
      }
    }

    private string ConvertCodeFieldReferenceExpression(CodeFieldReferenceExpression expr)
    {
      if (expr.TargetObject is CodeFieldReferenceExpression)
        // recurse
        return string.Format("{0}->{1}",
          ConvertCodeFieldReferenceExpression(
            expr.TargetObject as CodeFieldReferenceExpression
          ),
          expr.FieldName
         );
      else if (expr.TargetObject is CodeThisReferenceExpression)
        return string.Format("this->{0}", expr.FieldName);
      else if (expr.TargetObject is CodeTypeReferenceExpression)
        return string.Format("{0}::{1}", (expr.TargetObject as CodeTypeReferenceExpression).Type.BaseType, expr.FieldName);
      else
        throw new NotImplementedException();
    }
  }
}
